import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChild,
  ElementRef,
  EventEmitter,
  forwardRef,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  Output,
  Renderer2,
  SimpleChanges,
  TemplateRef,
  TrackByFunction,
  ViewChild,
  ViewContainerRef,
  ViewEncapsulation,
} from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ConnectedPosition, Overlay, OverlayConfig } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { Subject, Subscription } from 'rxjs';
import { debounceTime, filter, takeUntil, tap } from 'rxjs/operators';
import {
  animate,
  AnimationEvent,
  AnimationTriggerMetadata,
  group,
  style,
  transition,
  trigger,
} from '@angular/animations';
import { OverlayService } from '../core/overlay/overlay.service';
import { LvSelectSM, LvSizeLSD } from '../core/types';
import { InputBoolean, InputNumber } from '../core/utils/convert';
import { LvConfig } from '../core/config';
import { I18NService } from '../core/i18n/api';
import { MotionDuration, MotionDurationTime, MotionFunc, SelectRemoveChangeProps, showItem } from '../core/api';
import { CascaderService } from './cascader.service';
import { CascaderOption } from './interface';
import { CascaderTriggerComponent } from './cascader-trigger.component';

const POSITION: ConnectedPosition[] = [
  {
    originX: 'start',
    originY: 'bottom',
    overlayX: 'start',
    overlayY: 'top',
    offsetY: -1,
  },
  {
    originX: 'start',
    originY: 'top',
    overlayX: 'start',
    overlayY: 'bottom',
    offsetY: 1,
  },
];

/**
 * 子菜单动画
 */
const showSubItem: AnimationTriggerMetadata = trigger('showSubItem', [
  transition(
    ':enter',
    group([
      style({ transform: 'translateX(-0.1rem)', opacity: 0 }),
      animate(`${MotionDuration.XS} ${MotionFunc.EASE}`, style({ transform: 'translateX(0)' })),
      animate(`${MotionDuration.XS} ${MotionFunc.EASE_IN_OUT}`, style({ opacity: 1 })),
    ]),
  ),
  transition(
    ':leave',
    group([
      animate(`${MotionDuration.XS} ${MotionFunc.EASE}`, style({ transform: 'translateX(0)' })),
      animate(`${MotionDuration.XS} ${MotionFunc.EASE_IN_OUT}`, style({ opacity: 0 })),
    ]),
  ),
]);

@Component({
  selector: 'lv-cascader',
  templateUrl: './cascader.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof CascaderComponent => CascaderComponent),
      multi: true,
    },
    CascaderService,
  ],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  animations: [showItem, showSubItem],
  host: {
    class: 'lv-cascader',
    '[class.lv-cascader-disabled]': 'lvDisabled',
    '(click)': "lvTrigger === 'click' && handleClick($event)",
    '(mouseenter)': "lvTrigger === 'hover' && handleHover($event, true)",
    '(mouseleave)': "lvTrigger === 'hover' && handleHover($event, false)",
  },
})
export class CascaderComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges {
  @Input() lvSize: LvSizeLSD = 'default'; // NEED_DO: small

  @Input() lvMode: LvSelectSM = 'single'; // 选择模式，默认单选

  @Input() lvTrackBy: TrackByFunction<any>; // 内容列表trackBy

  @Input() lvOptions: CascaderOption[] = []; // 源数据

  @Input() @InputBoolean() lvLoading = false; // 是否加载中

  @Input() @InputBoolean() lvShowPath = true; // 是否显示选中项完整路径

  @Input() @InputBoolean() lvDisabled = false; // 是否禁用

  @Input() @InputBoolean() lvShowFilter = false; // 是否展示过滤

  @Input() @InputBoolean() lvFilterFocus = true; // 过滤输入框自动聚焦

  @Input() @InputBoolean() lvShowTrigger = true;

  @Input() @InputNumber() lvMaxTagCount: number; // 选中项个数模式下，最多显示多少个tag

  @Input() lvTrigger: 'click' | 'hover' = 'click';

  @Input() @InputNumber() lvMaxTagRow: number = 7; // 选中项单行模式下，点了+n之后，超过7（包含收起，可配置）行则出现滚动条

  @Input() @InputBoolean() lvChangeOnSelect = false; // 点击每层选项时，值都会发生变化，允许选择父级选项

  @Output() lvClearChange = new EventEmitter<any>(); // 清除全部选项时回调事件

  @Output() lvRemoveChange = new EventEmitter<any>(); // 删除单个时回调事件

  @Output() lvOpenChange = new EventEmitter<boolean>(); // 下拉面板展开关闭变更回调事件

  @Output() lvFilterChange = new EventEmitter<string>(); // 搜索文本框变化时回调事件

  @Input() lvOptionRender: TemplateRef<{ $implicit: any }>; // 自定义内容模板

  @ViewChild('cascaderPanelTpl') cascaderPanelTpl: TemplateRef<any>;

  @Input() lvPlaceholder: string = this.i18n.get('selectPlaceholder'); // 如果没有选择任何值，显示的占位符

  @ContentChild(CascaderTriggerComponent) customTrigger: CascaderTriggerComponent;

  @Input() @InputBoolean() lvShowClear = LvConfig.cascaderOptions.lvShowClear || false; // 是否显示清除按钮

  @Output() lvSelectionChange = new EventEmitter<CascaderOption[] | CascaderOption[][]>();

  overlayRef;

  showSubItem = true;
  filterValue: string; // 记录搜索框值

  open: boolean = false; // 下拉面板展开状态
  _direction: 'up' | 'down';
  selectedValue: string | string[];

  value: any = [];
  // 动效
  animationState: 'showUp' | 'showDown' | 'hideUp' | 'hideDown' | 'void' = 'void';
  private _activeElement: any;

  private destroy$ = new Subject<void>();
  private searchSubscribe = Subscription.EMPTY;
  private searchValueChange$ = new Subject<string>();
  constructor(
    private i18n: I18NService,
    public cascaderService: CascaderService,
    private overlay: Overlay,
    private overlayService: OverlayService,
    private elementRef: ElementRef,
    private viewContainerRef: ViewContainerRef,
    private renderer: Renderer2,
    private cdr: ChangeDetectorRef,
  ) {
    this._activeElement = elementRef.nativeElement;
  }

  get multiple(): boolean {
    return this.cascaderService.isMultipleMode();
  }

  // options渲染的下拉面板数据
  get columns(): CascaderOption[][] {
    return this.cascaderService.columns;
  }

  // 实际渲染的下拉选择项
  get renderOptions(): CascaderOption[][] {
    return this.cascaderService.searching ? this.cascaderService.searchColumns : this.cascaderService.columns;
  }

  ngAfterViewInit(): void {
    this.subscribeOpenChange();
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  setRefresh(): void {
    this.cascaderService.refresh$.next();
  }

  stopPropagation(event: MouseEvent): void {
    event.stopPropagation();
  }

  /**
   * 更新是否展开下拉面板，没有任何判断条件（外部可调用）
   */
  updateOpen(open: boolean): void {
    this.cascaderService.open$.next(open);
  }

  searchChange(value: string): void {
    this.searchValueChange$.next(value);
  }

  writeValue(value: Array<number | string> | Array<Array<number | string>>): void {
    this.setSelection(value);
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
    this.cascaderService.refresh$.complete();
    this.searchValueChange$.complete();
    this.closePanel();
  }

  setNodataPanelStyle(): void {
    this.overlayRef.overlayRef.hostElement
      .querySelector('.lv-cascader-no-data')
      ?.style.setProperty('width', `${this._activeElement.clientWidth}px`);
  }

  setOptionsMap(options: CascaderOption[], map): void {
    options.forEach((option): void => {
      map.set(option.value, option);

      if (option.children?.length) {
        this.setOptionsMap(option.children, map);
      }
    });
  }

  /**
   * 更新overlay位置
   */
  updatePosition(): void {
    setTimeout((): void => {
      if (this.existOverlayRef()) {
        this.overlayRef.overlayRef.updatePosition();
        // 设置无数据时的panel宽度和输入框保持一致
        this.setNodataPanelStyle();
        this.setRefresh();
      }
    });
  }

  onAnimationDone(event: AnimationEvent): void {
    const isUp = event.fromState === 'showUp' && event.toState === 'hideUp';
    const isDown = event.fromState === 'showDown' && event.toState === 'hideDown';
    if (isUp || isDown) {
      this.cascaderService.open$.next(false);
    }
  }

  /**
   * 获取选中项子节点的label
   */
  getLabelByValue(value: Array<number | string>): string {
    let current;
    let { options } = this.cascaderService;
    const optionsMap = new Map();
    this.setOptionsMap(options, optionsMap);

    value.forEach((item): void => {
      current = optionsMap.get(item) || '';
      options = current.children || [];
    });

    return current?.label || '';
  }

  clearSelection(event: Event): void {
    event.preventDefault();
    event.stopPropagation();

    this.cascaderService.selection = null;
    this.cascaderService.selectedOptions = [];
    this.cascaderService.setSearchActive();

    if (this.multiple) {
      this.cascaderService.resetOptionStatus();
    }

    this.closePanel();

    this.lvClearChange.emit();
  }

  getValue(): void {
    if (this.selectedValue) {
      this.value = this.multiple
        ? this.cascaderService.selection.map((item, index): any => ({
            label: this.selectedValue?.[index],
            value: item,
          }))
        : [{ label: this.selectedValue as string, value: this.cascaderService.selection as Array<string | number> }];
    } else {
      this.value = [];
    }
  }

  /**
   * 获取选中项的全路径字符串
   */
  getPathLabelByValue(value: Array<number | string>): string {
    let path = '';
    let current;
    let { options } = this.cascaderService;
    const optionsMap = new Map();
    this.setOptionsMap(options, optionsMap);

    value.forEach((item, index): void => {
      current = optionsMap.get(item) || '';

      if (current) {
        path = path === '' ? path + current.label : `${path} / ${current.label}`;
      } else if (this.cascaderService.columns.length === index + 1) {
        path = path === '' ? path + item : `${path} / ${item}`;
      }

      options = current.children || [];
    });

    return path;
  }

  ngOnInit(): void {
    this.cascaderService.initOptions(this.lvOptions);

    this.cascaderService.selectionChange$.pipe(takeUntil(this.destroy$)).subscribe((res): void => {
      this.setSelection(res);

      this.onChange(res);
      this.lvSelectionChange.emit(this.cascaderService.selectedOptions);
    });

    this.cascaderService.redraw$.pipe(takeUntil(this.destroy$)).subscribe((): void => {
      this.updatePosition();
    });

    this.cascaderService.refresh$.pipe(debounceTime(1), takeUntil(this.destroy$)).subscribe((): void => {
      if (this.multiple) {
        this.cascaderService.setMultiSelection();
      }
      this.cdr.markForCheck();
    });
  }

  /**
   * 设置选中项
   */
  setSelection(value: any): void {
    if (!value) {
      this.selectedValue = '';
      this.getValue();
      return;
    }

    if (this.multiple) {
      this.selectedValue = value.map((item): string =>
        this.lvShowPath ? this.getPathLabelByValue(item) : this.getLabelByValue(item),
      );
    } else {
      this.selectedValue = this.lvShowPath
        ? this.getPathLabelByValue(value as Array<number | string>)
        : this.getLabelByValue(value as Array<number | string>);
    }
    this.cascaderService._selection = value; // 适配初始化外部传入值
    if (!this.open) {
      // 面板打开的时候不需要更新面板折叠状态
      this.cascaderService.updateOptionsBySelection();
    }
    this.getValue();
  }

  ngOnChanges(changes: SimpleChanges): void {
    const { lvMode: modeChange, lvOptions: optionsChange, lvShowFilter: showFilterChange } = changes;

    if (modeChange) {
      this.cascaderService.setMode(modeChange.currentValue);
    }

    if (optionsChange && !optionsChange.firstChange) {
      this.cascaderService.initOptions(this.lvOptions);
    }

    if (showFilterChange) {
      this.searchSubscribe.unsubscribe();

      this.searchSubscribe = this.searchValueChange$
        .pipe(debounceTime(300), takeUntil(this.destroy$))
        .subscribe((res): void => {
          if (res) {
            this.showSubItem = false;
          } else {
            setTimeout((): void => {
              this.showSubItem = true;
            }, MotionDurationTime.XS);
          }
          this.cascaderService.search(res);
          this.lvFilterChange.emit(res);
        });
    }
  }

  removeSelectedItem({ event, node }: SelectRemoveChangeProps): void {
    event.stopPropagation();
    if (this.lvDisabled) {
      return;
    }
    if (this.open) {
      const removeMultiOption = (options: CascaderOption[], deep: number): void => {
        const option = options.find((item): boolean => item.value === node.value[deep]);
        if (option.children && !option.isLeaf) {
          removeMultiOption(option.children, deep + 1);
          option.selected = option.children.some((item): boolean => item.selected);
        } else {
          option.selected = false;
        }
      };
      removeMultiOption(this.lvOptions, 0);
    } else {
      const index = this.cascaderService._selection.findIndex((item): boolean => item === node.value);
      this.cascaderService._selection.splice(index, 1);
      this.cascaderService.selection = this.cascaderService._selection;
    }
    this.cascaderService.refresh$.next();
    setTimeout((): void => {
      this.updatePosition();
    });
  }

  beforeOpen = (): void => {
    this.cascaderService.updateOptionsBySelection(); // NEED_DO: 优化
  };

  afterOpen = (overlayInstance): void => {
    this.overlayRef = overlayInstance;
    // 设置z-index
    this.renderer.addClass(overlayInstance.overlayRef.hostElement, 'lv-cascader-panel-z-index');
    // hover 时监听panel鼠标进入
    if (this.lvTrigger === 'hover') {
      this.renderer.listen(overlayInstance.overlayRef.overlayElement, 'mouseenter', (event): void => {
        this.handleHover(event, true);
      });
      this.renderer.listen(overlayInstance.overlayRef.overlayElement, 'mouseleave', (event): void => {
        this.handleHover(event, false);
      });
    }
    // 设置无数据时的panel宽度和输入框保持一致
    this.setNodataPanelStyle();
    // 获取打开的方向
    overlayInstance.overlayRef._positionStrategy.positionChanges.subscribe((e): void => {
      // 如果面板已经展开，不再改变动画效果
      if (this.open) {
        return;
      }
      const { originY } = e.connectionPair;
      if (originY === 'top') {
        this.animationState = 'showUp';
        this._direction = 'up';
      } else {
        this.animationState = 'showDown';
        this._direction = 'down';
      }
      this.open = true;
      this.cdr.markForCheck();
      this.lvOpenChange.emit(this.open);
    });
  };

  beforeClose = (): Promise<void> =>
    new Promise<void>((resolve): void => {
      if (this._direction === 'up') {
        this.animationState = 'hideUp';
      } else {
        this.animationState = 'hideDown';
      }
      this.cdr.markForCheck();
      const closeSubscribe = this.cascaderService.open$.subscribe((open): void => {
        if (!open) {
          closeSubscribe.unsubscribe();
          resolve();
        }
      });
    });

  afterClose = (): void => {
    this.open = false;
    this.lvOpenChange.emit(this.open);
    this.setRefresh();
  };

  private onTouch: any = (): void => {};

  private onChange: any = (): void => {};

  /**
   * 关闭下拉面板
   */
  private closePanel(): void {
    if (this.existOverlayRef()) {
      this.overlayRef.close();
    }
  }

  /**
   * 判断overlay是否存在
   */
  private existOverlayRef(): any {
    return this.overlayRef?.overlayRef.hasAttached();
  }

  /**
   * 点击 trigger 切换下拉（内部使用）
   */
  private handleClick(event: Event): void {
    event.stopPropagation();

    if (this.lvDisabled) {
      event.preventDefault();
      return;
    }

    this.updateOpen(!this.open);
  }

  private handleHover(event: Event, isEnter: boolean): void {
    event.stopPropagation();

    if (this.lvDisabled) {
      event.preventDefault();
      return;
    }

    this.updateOpen(isEnter);
  }

  private subscribeOpenChange(): void {
    this.cascaderService.open$
      .pipe(
        tap((res): void => {
          // 执行关闭
          if (!res) {
            this.closePanel();
            this.setRefresh();
          }
        }),
        filter((res): any => res), // 过滤关闭状态，只执行打开
        takeUntil(this.destroy$),
      )
      .subscribe((): void => this.openPanel());
  }

  /**
   * 打开下拉面板
   */
  private openPanel(): void {
    if (this.existOverlayRef()) {
      return;
    }

    const config = new OverlayConfig({
      hasBackdrop: false,
      scrollStrategy: this.overlay.scrollStrategies.block(),
      positionStrategy: this.overlay.position().flexibleConnectedTo(this.elementRef).withPositions(POSITION),
    });

    const extendConfig = {
      beforeOpen: this.beforeOpen,
      afterOpen: this.afterOpen,
      beforeClose: this.beforeClose,
      afterClose: this.afterClose,
    };

    const portal = new TemplatePortal(this.cascaderPanelTpl, this.viewContainerRef);
    this.overlayService.open(portal, config, extendConfig);
    this.setRefresh();
  }
}
